<?php
/**
 * 组件抽象类
 * 
 * 实现了IMintComponent的一些方法
 *
 * @version $Id: IAbstractMintComponent.php 312 2008-04-23 16:29:38Z iwind $
 * @package util.mint.component
 */
abstract class IAbstractMintComponent extends IObject implements IMintComponent {
	private $node;
	private $dataSet;
	private $template;
		
	/**
	 * 设置节点
	 *
	 * @param DOMElement $node DOM元素
	 */
	public function setNode(DOMElement $node) {
		$this->node = $node;
	}
	
	/**
	 * 设置能访问到的数据集
	 *
	 * @param IMintDataSet &$dataSet 数据集
	 */
	public function setDataSet(IMintDataSet &$dataSet) {
		$this->dataSet = &$dataSet;
	}
	
	/**
	 * 取得参数集
	 *
	 * @return array
	 */
	public function getParameters() {
		$supported = $this->getSupportedParameters();
		$supported[] = IMintConfig::$componentAttributeName;
		$supported[] = IMintConfig::$nodeKeepAttributeName;
		$parameters = array();
		foreach ($this->node->attributes as $attr) {
			$name = strToLower($attr->name);
			$value = $attr->value;
			if (in_array($name, $supported)) {
				$parameters[$name] = $value;
			}
		}
		return $parameters;
	}
	
	/**
	 * 取得当前组件所在的节点
	 *
	 * @return DOMElement
	 */
	public function getNode() {
		return $this->node;
	}
	
	/**
	 * 取得当前组件能使用的数据集
	 *
	 * @return IMintDataSet
	 */
	public function &getDataSet() {
		return $this->dataSet;
	}
	
	/**
	 * 取得所在的DOMDocument
	 *
	 * @return DOMDocument
	 */
	public function getDocument() {
		return $this->node->ownerDocument;
	}
	
	/**
	 * 删除组件的属性
	 *
	 * @param array $attributes 属性数组
	 */
	public function removeAttributes(array $attributes) {
		foreach ($attributes as $attribute) {
			$this->node->removeAttribute($attribute);
		}
	}
	
	/**
	 * 设置当前组件所在的模板
	 *
	 * @param IMintTemplate $template mint模板
	 */
	public function setTemplate(IMintTemplate $template) {
		$this->template = $template;
	}
	
	/**
	 * 取得模板
	 *
	 * @return IMintTemplate
	 */
	public function getTemplate() {
		return $this->template;
	}
	
	/**
	 * 处理组件
	 *
	 */
	public function process() {
		if ($this->processComponent()) {	
			//是否保留节点
			$parameters = $this->getParameters();
			if (isset($parameters[IMintConfig::$nodeKeepAttributeName]) 
						&& $parameters[IMintConfig::$nodeKeepAttributeName] === "false") {
				$this->removeNode();
			}
		}
		
		//删除模板中关于组建定义的属性
		$this->removeAttributes(array_keys($this->getParameters()));
		
		//删除映射中的属性
		$this->removeAttributes(array_keys($this->getHtmlAttrMapping()));
	}
	
	/**
	 * 取得支持的参数
	 *
	 * @return array
	 */
	public abstract function getSupportedParameters();
	
	/**
	 * 处理组件
	 * 
	 * 返回true为处理成功，false为处理失败，失败时将保留组件的参数
	 * 
	 * @return boolean
	 */
	public abstract function processComponent();
	
	/**
	 * 在第一个子节点前添加PHP脚本
	 *
	 * @param string $text 文本
	 * @since 0.0.3
	 */
	public function addFirstChildScript($text) {
		$textNode = IMintReplacement::addScript("\n" . $text);//加上换行
		$this->node->insertBefore($textNode, $this->node->firstChild); 
	}
	
	/**
	 * 在最后一个子节点后添加PHP脚本
	 *
	 * @param string $text 文本
	 * @since 0.0.3
	 */
	public function addLastChildScript($text) {
		$textNode = IMintReplacement::addScript($text);
		$this->node->appendChild($textNode);
	}	
	
	/**
	 * 添加PHP环绕脚本
	 *
	 * @param string $firstText 第一行文本
	 * @param string $lastText 最后一行文本
	 * @since 0.0.3
	 */
	public function addAroundScript($firstText, $lastText) {
		$this->addFirstChildScript($firstText);
		$this->addLastChildScript($lastText);
	}	
	
	/**
	 * 在第一个子节点前添加文本
	 *
	 * @param string $text 文本
	 */
	public function addFirstChildText($text) {
		$textNode = IMintReplacement::addText($text);
		$this->node->insertBefore($textNode, $this->node->firstChild); 
	}
	
	/**
	 * 在最后一个子节点后添加文本
	 *
	 * @param string $text 文本
	 */
	public function addLastChildText($text) {
		$textNode = IMintReplacement::addText($text);
		$this->node->appendChild($textNode);
	}
	
	/**
	 * 添加环绕文本
	 *
	 * @param string $firstText 第一行文本
	 * @param string $lastText 最后一行文本
	 */
	public function addAroundText($firstText, $lastText) {
		$this->addFirstChildText($firstText);
		$this->addLastChildText($lastText);
	}
	
	/**
	 * 删除定义当前组件的所在节点
	 * 
	 * @param boolean $keepChild 是否保留子节点，0.0.3加入此参数
	 * @return boolean
	 */
	public function removeNode($keepChild = true) {
		$parentNode = $this->node->parentNode;
		if ($parentNode == null) {
			return false;
		}
		if (!$keepChild) {
			$parentNode->removeChild($this->node);
			return true;
		}
		$childNodes = $this->node->childNodes;
		$parentNode->insertBefore(IMintReplacement::addText("\n"), $this->node);//加上换行
		foreach ($childNodes as $childNode) {
			$parentNode->insertBefore($childNode->cloneNode(true), $this->node);
		}
		$parentNode->removeChild($this->node);
		return true;
	}
	
	/**
	 * 替换文本节点(TextNode)
	 *
	 * @param string $newText 新的文本
	 * @param string $type 类型，从0.0.3版本加入
	 */
	public function replaceNodeText($newText, $type = "php") {
		if ($type == "php") {
			$textNode = IMintReplacement::addScript($newText);
		}
		else {
			$textNode = IMintReplacement::addText($newText);
		}
		$parentNode = $this->node->parentNode;
		$parentNode->replaceChild($textNode, $this->node);
	}
	
	/**
	 * 替换所有子节点内容文本
	 *
	 * @param string $newText 新的文本
	 * @param string $type 类型
	 * @since 0.1
	 */
	public function replaceChildText($newText, $type = "php") {
		if ($type == "php") {
			$textNode = IMintReplacement::addScript($newText);
		}
		else {
			$textNode = IMintReplacement::addText($newText);
		}
		$this->node->nodeValue = "";
		$this->node->appendChild($textNode);
	}
	
	/**
	 * 取得参数的值
	 *
	 * @param string $parameterName 参数名
	 * @return string
	 */
	public function getParameterValue($parameterName) {
		$parameters = $this->getParameters();
		if (isset($parameters[$parameterName])) {
			return $parameters[$parameterName];
		}
		return null;
	}
	
	/**
	 * 取得组件的名称
	 *
	 * @return string
	 */
	public function getName() {
		return $this->getParameterValue(IMintConfig::$componentAttributeName);
	}
	
	/**
	 * 取得节点的描述
	 *
	 * @return string
	 */
	public function getNodeDescription() {
		$description = array();
		$parameters = $this->getParameters();
		foreach ($this->node->attributes as $attr) {
			if (isset($parameters[$attr->name])) {
				$description[] = "<strong>" . $attr->name . "=\"" . $attr->value . "\"" . "</strong>";
			}
			else {
				$description[] = $attr->name . "=\"" . $attr->value . "\"";
			}
		}
		return str_replace("$", "&#36;", "<em>&lt;" . $this->node->tagName . " " .  implode(" ", $description) . "&gt;</em> in file <em>" . $this->getTemplate()->getTemplateFile()->getPath() . "</em>");
	}
	
	/**
	 * 抛出变量异常
	 *
	 * @param string $varname 变量名
	 * @since 0.0.3
	 */
	public function throwVariableException($varname) {
		throw new IMintException("'{$varname}' in component " . $this->getName() . " must be a valid variable<br/>\n <strong>component definition:</strong> " . $this->getNodeDescription());
	}
	
	/**
	 * 抛出参数值异常
	 *
	 * @param string $varname 变量名
	 * @since 0.0.3
	 */	
	public function throwUndefinedValueException($varname) {
		throw new IMintException("'{$varname}' parameter in component " . $this->getName() . " is undefined<br/>\n <strong>component definition:</strong> " . $this->getNodeDescription());
	}
	
	/**
	 * 抛出参数值不可用异常
	 *
	 * @param string $varname 变量名
	 * @param string $value 值
	 * @since 0.0.3
	 */	
	public function throwUnavailableException($varname, $value) {
		throw new IMintException("unavalilable value '{$value}' for '{$varname}' parameter in component " . $this->getName() . "<br/>\n <strong>component definition:</strong> " . $this->getNodeDescription());
	}	
	
	/**
	 * 抛出解析过程中的异常
	 *
	 * @since 0.0.3
	 */
	public function throwException($message) {
		throw new IMintException("{$message}<br/>\n <strong>component definition:</strong> " . $this->getNodeDescription());
	}
	
	/**
	 * 取得HTML属性映射
	 *
	 * @return array
	 * @since 0.0.3
	 */
	public abstract function getHtmlAttrMapping();
	
	/**
	 * 取得需要保留的属性
	 *
	 * @return array
	 * @since 0.0.3
	 */
	public function getKeepAttrs() {
		$node = $this->getNode();
		$mapping = $this->getHtmlAttrMapping();
		$nodeAttributeArray = $node->attributes;
		$keepAttributeArray = array();
		foreach ($nodeAttributeArray as $nodeAttribute) {
			$attrName = $nodeAttribute->nodeName;
			if (array_key_exists($attrName, $mapping)) {
				$keepAttributeArray[] = addcslashes($mapping[$attrName] . "=\"" . htmlspecialchars($nodeAttribute->nodeValue) . "\"", "\"");
			}
		}
		return implode(" ", $keepAttributeArray);
	}
	
	/**
	 * 取得节点内容
	 * 
	 * 对于HTML节点可能会出错
	 *
	 * @param boolean $containsRoot 是否包含根节点
	 * @return string
	 * @since 0.0.3
	 */
	public function saveNodeToString($containsRoot = false) {
		if ($containsRoot) {
			return $this->getDocument()->saveXML($this->node);
		}
		$string = "";
		foreach($this->node->childNodes as $childNode) {
			 $string .= $this->getDocument()->saveXML($childNode);	
		}
		return $string;
	}
	
	/**
	 * 取得当前节点的父节点
	 *
	 * @param  string|null $nodeType 节点类型
	 * @return DOMNode
	 * @since 0.0.3
	 */
	public function getParentNode($nodeType = XML_ELEMENT_NODE) {
		if ($nodeType == null) {
			return $this->node->parentNode;
		}
		else {
			$node = $this->node->parentNode;
			if ($node->nodeType != $nodeType) {
				while (($node = $node->parentNode) != null) {
					if ($node->nodeType == $nodeType) {
						return $node;
					}
				}
			}
			else {
				return $node;
			}
		}
	}
	
	/**
	 * 在节点上部添加脚本
	 *
	 * @param string $script 脚本
	 * @since 0.0.3
	 */
	public function addAboveScript($script) {
		$this->getParentNode()->insertBefore(IMintReplacement::addScript($script), $this->node);
	}
	
	/**
	 * 在节点下部添加脚本
	 *
	 * @param string $script 脚本
	 * @since 0.0.3
	 */	
	public function addBelowScript($script) {
		$this->getParentNode()->insertBefore(IMintReplacement::addScript($script), $this->node->nextSibling);
	}
	
	/**
	 * 检验
	 * 
	 * 如需自定义检验，可以覆盖该方法
	 *
	 * @return boolean
	 */
	public function validate() {
		return true;
	}
	
	/**
	 * 取得当前节点的父节点
	 *
	 * @param  string|null $nodeType 节点类型
	 * @return DOMNode
	 * @since 0.0.3
	 */	
	public function getPreviousNode($nodeType = XML_ELEMENT_NODE) {
		if ($nodeType == null) {
			return $this->node->previousSibling;
		}
		else {
			$node = $this->node->previousSibling;
			if ($node->nodeType != $nodeType) {
				while (($node = $node->previousSibling) != null) {
					if ($node->nodeType == $nodeType) {
						return $node;
					}
				}
			}
			else {
				return $node;
			}
		}
	}
	
	/**
	 * 判断节点是否包含yield变量
	 * 
	 * yield变量代表了需要替换的占位变量，在mint中一般用于存放处理过程，比如
	 * <select com="@options" x-options="$categories">
	 * <option>请选择</option
	 * {$yield}
	 * <option> -- </option>
	 * </select>
	 * 处理后，可能变成
	 * <select com="@options" x-options="$categories">
	 * <option>请选择</option
	 * <?php 
	 * foreach($categories as $category) {
	 * ...
	 * }
	 * ?> 
	 * <option> -- </option>
	 * </select>
	 * 
	 * 如果嵌套的组件中有两个命名一样的yield，就会导致意想不到的错误
	 * 
	 * @since 0.1
	 */
	public function containsYield($yield = "yield") {
		$text = $this->saveNodeToString();
		$var = "{\$" . $yield . "}";
		if (strstr($text, $var)) {
			return true;
		}
		return false;
	}
	
	/**
	 * 替换yield变量
	 *
	 * @param string $yield 字符串
	 * @param string $process 过程
	 * @since 0.1
	 */
	public function replaceYield($yield, $process) {
		$text = $this->saveNodeToString();
		$var = "{\$" . $yield . "}";
		$text = str_replace($var, $process, $text);
		$this->replaceChildText($text);
	}
	
	/**
	 * 取得一个不重复的变量名
	 *  
	 * 不包含美元（$）符号
	 * 
	 * @return string
	 * @since 0.1
	 */
	public function getUniqid() {
		return "_" . uniqid();
	}
}

?>